package zb.ws.api;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import com.alibaba.fastjson.JSONObject;
import com.jfinal.kit.PathKit;
import com.neovisionaries.ws.client.ProxySettings;
import com.neovisionaries.ws.client.WebSocket;
import com.neovisionaries.ws.client.WebSocketAdapter;
import com.neovisionaries.ws.client.WebSocketException;
import com.neovisionaries.ws.client.WebSocketFactory;
import com.neovisionaries.ws.client.WebSocketFrame;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import entity.commons.UserApiEntity;
import jodd.io.FileUtil;
import jodd.template.MapTemplateParser;
import jodd.typeconverter.Converter;
import kits.my.BufferKit;
import kits.my.EncryDigestUtil;
import lombok.val;
import lombok.extern.slf4j.Slf4j;
import zb.ws.adapter.IAdapter;

@Slf4j
public class WsZb {
	private IAdapter iAdapter;

	private UserApiEntity userKey;
	public WsZb(UserApiEntity userKey) {
		super();
		this.userKey = userKey;
	}

	public WsZb(UserApiEntity userKey, IAdapter iAdapter) {
		super();
		this.userKey = userKey;
		this.iAdapter = iAdapter;

		this.connect();
		do {
			ThreadUtil.sleep(1000);
			log.info("--检查握手..." + new Date());
		} while (ws == null);
	}

	private WebSocket ws;
	private boolean activite;
	private Status status;
 

	private Runnable mReconnectTask = new Runnable() {
		@Override
		public void run() {
			WebSocketFactory factory = new WebSocketFactory();
			ProxySettings settings = factory.getProxySettings();

			settings.setHost("127.0.0.1").setPort(9524);

			try {
				ws = factory.createSocket("wss://api.zb.com:9999/websocket").setFrameQueueSize(5)// 设置帧队列最大值为5;
//				ws = factory.createSocket("ws://127.0.0.1:8580/websocket").setFrameQueueSize(5)// 设置帧队列最大值为5;
						.setMissingCloseFrameAllowed(false);// 设置不允许服务端关闭连接却未发送关闭帧
				// .connectAsynchronously();////异步连接
				ws.addListener(new Adapter());//// 添加回调监听
				ws.connect();
				status = Status.connecting;
			} catch (IllegalArgumentException | IOException e) {
				e.printStackTrace();
			} catch (WebSocketException e) {
				e.printStackTrace();
			}
		}
	};

	private int reconnectCount = 0;// 重连次数
	private long minInterval = 3000;// 重连最小时间间隔
	private long maxInterval = 60000;// 重连最大时间间隔
	private ScheduledExecutorService executor = null;

	public void connect() {
		// 这里其实应该还有个用户是否登录了的判断 因为当连接成功后我们需要发送用户信息到服务端进行校验
		// 由于我们这里是个demo所以省略了
		if (ws != null && ws.isOpen()) {
			log.info("目前处于链接状态");
			return;
		}
		if (status == Status.connecting) {// 不是正在重连状态
			log.info("正在链接中..");
			return;
		}
		status = Status.connecting;
		reconnectCount++;

		long reconnectTime = minInterval;
		if (reconnectCount > 3) {
			long temp = minInterval * (reconnectCount - 2);
			reconnectTime = temp > maxInterval ? maxInterval : temp;
		}

		// 按指定频率周期执行某个任务。初始化延迟0ms开始执行，每隔100ms重新执行一次任务。
		executor = Executors.newScheduledThreadPool(1);
		executor.scheduleAtFixedRate(mReconnectTask, 0, reconnectTime, TimeUnit.MILLISECONDS);
	}

	private void cancelReconnect() {
		reconnectCount = 0;
		executor.shutdown();
	}

	private class Adapter extends WebSocketAdapter {
		@Override
		public void onTextMessage(WebSocket ws, String message) {
			iAdapter.onReceive(ws, message);
		}

		@Override
		public void onConnected(WebSocket websocket, Map<String, List<String>> headers) throws Exception {
			super.onConnected(websocket, headers);
			log.info("连接成功");
			status = Status.connectSuccess;
			cancelReconnect();// 连接成功的时候取消重连,初始化连接次数
			// 需要重新加载
			channels.forEach(channel -> {
				ws.sendText(channel);
			});
		}

		@Override
		public void onConnectError(WebSocket websocket, WebSocketException exception) throws Exception {
			super.onConnectError(websocket, exception);
			log.info("连接错误");
			status = Status.connectFail;
			connect();// 连接错误的时候调用重连方法
		}

		@Override
		public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame,
				WebSocketFrame clientCloseFrame, boolean closedByServer) throws Exception {
			super.onDisconnected(websocket, serverCloseFrame, clientCloseFrame, closedByServer);
			log.info("断开连接");
			status = Status.connectFail;
			connect();// 连接断开的时候调用重连方法
		}
	}

	// 如果重新链接需要重新订阅
	public static List<String> channels = new ArrayList<String>();

	/** 新增订阅,给行情接口使用 */
	public void saveChannel(String channel) {
		String str = "{'event':'addChannel','channel':'" + channel + "'}";// + "_depth
		System.out.println(str);
		WebSocket sendText = ws.sendText(str);
		System.out.println(sendText.toString());
		channels.add(str);
	}
	
	public void depth() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", "ltcqc_depth");
		ws.sendText(JSONObject.toJSONString(data));
	}
	
	public void ticker() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", "ltcbtc_ticker");
		ws.sendText(JSONObject.toJSONString(data));
	}
	
	public void trades() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", "ltcbtc_trades");
		ws.sendText(JSONObject.toJSONString(data));
	}
	
	public void markets() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", "markets");
		ws.sendText(JSONObject.toJSONString(data));
	}
	
	
	

	public void getAccount() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", "getaccountinfo");
		data.put("accesskey", userKey.getApiKey());
		
		this.send(data);
	}

	private void send(Map<String, String> data) {
		data.put("no", "test001");
		String secret = EncryDigestUtil.digest(userKey.getSecretKey());
		String jsonString = JSONObject.toJSONString(data);
		String sign = EncryDigestUtil.hmacSign(jsonString, secret);
		data.put("sign", sign);
		jsonString = JSONObject.toJSONString(data);
//		ws.sendText(jsonString);
//		this.gen(data);
		this.genPython(data);
	}
	
	private void genPython(Map<String, String> data) {
		Map<String, String> clone = ObjectUtil.clone(data);
		clone.remove("sign");
		String temp = "	data.put(\"{}\", \"{}\");";
		val sbp = new BufferKit();
		
		val params = new TreeMap<String, String>();
		clone.forEach((k,v)->{
			sbp.append(temp,k,v);
		});
		
		params.put("method", data.get("channel"));
		params.put("apiKey", data.get("accesskey"));
		params.put("secretKey", userKey.getSecretKey());
		params.put("params", sbp.toString());
		String jsonString = JSONObject.toJSONString(clone);
		params.put("paramsStr", jsonString);
		String secret = EncryDigestUtil.digest(userKey.getSecretKey());
		params.put("secret", secret);
		String sign = EncryDigestUtil.hmacSign(jsonString, secret);
		params.put("sign", sign);
		data.put("sign", sign); 
		params.put("requestJson", JSONObject.toJSONString(data));
		String template = PathKit.getRootClassPath() + "/wsPython.template";
		try {
			String html = new MapTemplateParser().of(params).parse(FileUtil.readString(template));
			System.out.println(html);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	private void gen(Map<String, String> data) {
		Map<String, String> clone = ObjectUtil.clone(data);
		clone.remove("sign");
		String temp = "	data.put(\"{}\", \"{}\");";
		val sbp = new BufferKit();
		
		val params = new TreeMap<String, String>();
		clone.forEach((k,v)->{
			sbp.append(temp,k,v);
		});
		
		params.put("method", data.get("channel"));
		params.put("apiKey", data.get("accesskey"));
		params.put("secretKey", userKey.getSecretKey());
		params.put("params", sbp.toString());
		String jsonString = JSONObject.toJSONString(clone);
		params.put("paramsStr", jsonString);
		String secret = EncryDigestUtil.digest(userKey.getSecretKey());
		params.put("secret", secret);
		String sign = EncryDigestUtil.hmacSign(jsonString, secret);
		params.put("sign", sign);
		data.put("sign", sign);
		params.put("requestJson", JSONObject.toJSONString(data));
		String template = PathKit.getRootClassPath() + "/wsJava.template";
		try {
			String html = new MapTemplateParser().of(params).parse(FileUtil.readString(template));
			System.out.println(html);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void buy(String coin, double price, double amount) {
		this.order(price, amount, coin, 1);
	}

	public void sell(String coin, double price, double amount) {
		this.order(price, amount, coin, 0);
	}
	
	public void cancel(String symbol, long orderId, String no) {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", symbol.replace("_", "") + "_cancelorder");
		data.put("event", "addChannel");
		data.put("id", Converter.get().toString(orderId));
		data.put("no", no);
		this.send(data);
	}

	public void getOrder(String symbol, long orderId, String no) {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", symbol.replace("_", "") + "_getorder");
		data.put("event", "addChannel");
		data.put("id", Converter.get().toString(orderId));
		data.put("no", no);
		this.send(data);
	}

	private void order(double price, double amount, String symbol, int tradeType) {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", Convert.toStr(amount));
		data.put("channel", symbol.replace("_", "") + "_order");
		data.put("event", "addChannel");
		data.put("price", Convert.toStr(price));
		data.put("tradeType", Convert.toStr(tradeType));
		this.send(data);
	}
	
	public void getOrders(int pageIndex, int tradeType, String coin) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", coin.toLowerCase().replace("_", "") + "_getorders");
		data.put("event", "addChannel");
		data.put("pageIndex", Convert.toStr(pageIndex));
		data.put("tradeType", Convert.toStr(tradeType));
		data.put("acctType", "0");

		this.send(data);
	}

	/**
	 * (新)获取多个委托买单或卖单，每次请求返回pageSize<=100条记录
	 * 
	 * @param userKey.getApiKey()
	 * @param secretKey
	 * @param pageIndex
	 * @param pageSize
	 * @param coin
	 * @throws Exception
	 */
	public void getOrdersNew(int pageIndex, int pageSize, int tradeType, String coin) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		
		data.put("channel", coin.toLowerCase().replace("_", "") + "_getordersnew");
		data.put("event", "addChannel");
		
		data.put("pageIndex", Convert.toStr(pageIndex));
		data.put("pageSize", Convert.toStr(pageSize));
		data.put("tradeType", Convert.toStr(tradeType));

		this.send(data);
	}

	/**
	 * 与getOrdersNew的区别是取消tradeType字段过滤，可同时获取买单和卖单，每次请求返回pageSize<=100条记录
	 * 
	 * @param userKey.getApiKey()
	 * @param secretKey
	 * @param pageIndex
	 * @param pageSize
	 * @param coin
	 * @throws Exception
	 */
	public void getOrdersIgnoreTradeType(int pageIndex, int pageSize, String coin) throws Exception {

		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		
		data.put("channel", coin.toLowerCase().replace("_", "") + "_getordersignoretradetype");
		data.put("event", "addChannel");
		data.put("pageIndex", Convert.toStr(pageIndex));
		data.put("pageSize", Convert.toStr(pageSize));

		this.send(data);
	}

	/**
	 * 获取未成交或部份成交的买单和卖单，每次请求返回pageSize<=100条记录
	 * 
	 * @param userKey.getApiKey()
	 * @param secretKey
	 * @param pageIndex
	 * @param pageSize
	 * @param coin
	 * @throws Exception
	 */
	public void getUnfinishedOrdersIgnoreTradeType(int pageIndex, int pageSize, String coin) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", coin.toLowerCase().replace("_", "") + "_getunfinishedordersignoretradetype");
		data.put("event", "addChannel");
		data.put("pageIndex", Convert.toStr(pageIndex));
		data.put("pageSize", Convert.toStr(pageSize));
		this.send(data);
	}
	
	/** 获取杠杆账单 */
	public void getLeverBills(String coin) {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "getLeverBills");
		data.put("coin", coin);
		data.put("event", "addChannel");
		
		data.put("pageIndex", Convert.toStr(1));
		data.put("pageSize", Convert.toStr(10));
		
		this.send(data);
	}
	
	/**
	 * 获取认证的提现地址
	 * 
	 * @param userKey.getApiKey()
	 * @param coin
	 * @throws Exception
	 */
	public void getWithdrawAddress(String coin) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", coin.toLowerCase() + "_getwithdrawaddress");
		data.put("accesskey", userKey.getApiKey());

		this.send(data);
	}

	/**
	 * 获取提现的记录
	 * 
	 * @param userKey.getApiKey()
	 * @param coin
	 * @throws Exception
	 */
	public void getWithdrawRecord(String coin, int pageIndex, int pageSize) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", coin.toLowerCase() + "_getwithdrawrecord");
		data.put("accesskey", userKey.getApiKey());
		data.put("pageIndex", Convert.toStr(pageIndex));
		data.put("pageSize", Convert.toStr(pageSize));

		this.send(data);
	}

	/**
	 * 取消提现
	 * 
	 * @param userKey.getApiKey()
	 * @param coin
	 * @throws Exception
	 */
	public void cancelWithdraw(String coin, String downloadId) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("channel", coin.toLowerCase() + "_cancelwithdraw");
		data.put("accesskey", userKey.getApiKey());
		data.put("downloadId", downloadId);
		data.put("safePwd", userKey.getPayPass());

		this.send(data);
	}
	
	public void getLeverAssetsInfo() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "getuserLeverAsset");
		data.put("event", "addChannel");
		
		
		data.put("no", null);

		this.send(data);
	}


	/**
	 * 提现
	 * 
	 * @param coin
	 *            币种
	 * @param amount
	 *            提现金额
	 * @param fees
	 *            提现矿工费
	 * @param receiveAddr
	 *            提现地址
	 * @throws Exception
	 */
	public void withdraw(String coin, BigDecimal amount, BigDecimal fees, String receiveAddr) throws Exception {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", amount + "");
		data.put("channel", "qc_withdraw");
		data.put("event", "addChannel");
		
		data.put("fees", fees + "");
		data.put("receiveAddr", receiveAddr);
		data.put("safePwd", userKey.getPayPass());

		this.send(data);
	}
	
	/**资产转入杠杠区*/
	public void transferInLever() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", Convert.toStr(100));
		
		data.put("channel", "transferInLever");
		data.put("coin", "qc");
		data.put("event", "addChannel");
		
		
		
		data.put("marketName", "btsqc");
		
		this.send(data);
	}
	
	public void transferOutLever() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", Convert.toStr(100));
		data.put("channel", "transferOutLever");
		data.put("coin", "qc");
		data.put("event", "addChannel");
		
		data.put("marketName", "btsqc");
		
		this.send(data);
	}
	
	/**发布理财*/
	public void loan() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", Convert.toStr(100));
		data.put("channel", "loan");
		data.put("coin", "qc");
		data.put("event", "addChannel");

		data.put("interestRateOfDay", "0.15");
		data.put("isLoop", "1");
		data.put("repaymentDay", "20");
		data.put("safePwd", "123456");
		
		this.send(data);
	}
	
	/**取消理财*/
	public void cancelLoan() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "cancelLoan");
		data.put("event", "addChannel");
		
		
		data.put("loanId", "121");
		
		this.send(data);
	}
	/**获取可借贷列表*/
	public void getLoans() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "getLoans");
		data.put("coin", "qc");
		data.put("event", "addChannel");
		
		
		data.put("pageIndex", Convert.toStr(1));
		data.put("pageSize", Convert.toStr(10));
		
		this.send(data);
	}
	
	/**获取借贷记录*/
	public void getLoanRecords() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "getLoanRecords");
		data.put("event", "addChannel");
		
		
		data.put("loanId", Convert.toStr(58));
		data.put("marketName", "btsqc");
		data.put("pageIndex", Convert.toStr(1));
		data.put("pageSize", Convert.toStr(10));
		
		this.send(data);
	}
	
	public void borrow() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", Convert.toStr(100));
		data.put("channel", "borrow");
		data.put("coin", "qc");
		data.put("event", "addChannel");
		
		
		
		
		data.put("interestRateOfDay", "0.15");
		data.put("isLoop", "1");
		
		data.put("repaymentDay", Convert.toStr(20));
		data.put("safePwd", "123456");

		this.send(data);
	}
	
	public void repay() {
		val data = new TreeMap<String, String>();
		data.put("accesskey", userKey.getApiKey());
		data.put("amount", Convert.toStr(100));
		data.put("channel", "repay");
		
		data.put("event", "addChannel");
		
		data.put("loanRecordId", Convert.toStr(292));
		data.put("repayAmount", Convert.toStr(50));
		data.put("repayType", Convert.toStr(0));

		this.send(data);
	}
	/**获取还款记录*/
	public void getRepayments() {
		val data = new TreeMap<String, String>();
		
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "getRepayments");
		data.put("event", "addChannel");
		data.put("loanRecordId", Convert.toStr(292));
		
		data.put("pageIndex", Convert.toStr(1));
		data.put("pageSize", Convert.toStr(10));
		
		this.send(data);
	}
	/**获取理财记录*/
	public void getFinanceRecords() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("accesskey", userKey.getApiKey());
		data.put("channel", "getFinanceRecords");
		data.put("coin", "qc");
		
		data.put("pageIndex", Convert.toStr(1));
		data.put("pageSize", Convert.toStr(10));
		
		this.send(data);
	}
	/**创建子账号*/
	public void addSubUser() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("accesskey", userKey.getApiKey());
		data.put("channel","addSubUser");
		data.put("memo","memo");
		data.put("password","123456");
		data.put("subUserName","test1");
		
		this.send(data);
	}
	
	/**创建子账号API*/
	public void createSubUserKey() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("accesskey", userKey.getApiKey());
		data.put("channel","createSubUserKey");
		data.put("assetPerm","true");
		data.put("entrustPerm","true");
		data.put("keyName","xxxxx");
		data.put("leverPerm","xxx");
		data.put("moneyPerm","true");
		data.put("toUserId","xxx");
		this.send(data);
	}
	
	/**获取子账号列表*/
	public void getSubUserList() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("accesskey", userKey.getApiKey());
		data.put("channel","getSubUserList");
		this.send(data);
	}
	/**主子账号内部转账*/
	public void doTransferFunds() {
		val data = new TreeMap<String, String>();
		data.put("event", "addChannel");
		data.put("accesskey", userKey.getApiKey());
		data.put("channel","doTransferFunds");
		data.put("amount","1");
		data.put("currency","zb");
		data.put("fromUserName","xxxx");
		data.put("toUserName","xxxx");
		this.send(data);
	}
	
	

}
